jetcrab\vm\executor\instruction_handlers/comparison.rs
1//! # Comparison Handler
2//!
3//! Handles all comparison and logical operations in the VM including equality checks,
4//! relational comparisons, and logical operations.
5//!
6//! ## Operations Supported
7//!
8//! - **Equality**: equal, not_equal, strict_equal, strict_not_equal
9//! - **Relational**: less_than, less_equal, greater_than, greater_equal
10//! - **Logical**: logical_and, logical_or, logical_not
11//! - **Bitwise**: bitwise_and, bitwise_or, bitwise_xor, bitwise_not
12//!
13//! ## Comparison Rules
14//!
15//! - **Loose Equality**: Uses JavaScript-like type coercion
16//! - **Strict Equality**: No type coercion, exact value comparison
17//! - **Relational**: Numeric comparison with type coercion
18//! - **Logical**: Boolean operations with truthy/falsy conversion
19//!
20//! ## Usage
21//!
22//! ```rust
23//! use jetcrab::vm::executor::instruction_handlers::ComparisonHandler;
24//! use jetcrab::vm::executor::traits::StackOperations;
25//!
26//! let mut stack = MyStack::new();
27//! stack.push(Value::Number(5.0));
28//! stack.push(Value::Number(3.0));
29//! ComparisonHandler::greater_than(&mut stack)?;
30//! // Stack now contains: [true]
31//! ```
32
33use crate::vm::executor::error_handler::ExecutionError;
34use crate::vm::executor::traits::StackOperations;
35use crate::vm::value::Value;
36
37/// Handles comparison and logical operations for the VM
38pub struct ComparisonHandler;
39
40impl ComparisonHandler {
41 /// Checks if two values are equal using loose equality
42 ///
43 /// Pops two values from the stack, compares them using JavaScript-like
44 /// type coercion, and pushes the boolean result.
45 ///
46 /// # Arguments
47 /// * `stack` - The stack to operate on
48 ///
49 /// # Returns
50 /// * `Ok(())` on success
51 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
52 ///
53 /// # Examples
54 ///
55 /// ```rust
56 /// let mut stack = MyStack::new();
57 /// stack.push(Value::Number(5.0));
58 /// stack.push(Value::String("5".to_string()));
59 /// ComparisonHandler::equal(&mut stack)?;
60 /// assert_eq!(stack.pop(), Some(Value::Boolean(true)));
61 /// ```
62 pub fn equal<S>(stack: &mut S) -> Result<(), ExecutionError>
63 where
64 S: StackOperations,
65 {
66 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
67 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
68
69 let result = match (a, b) {
70 (Value::Number(a), Value::Number(b)) => a == b,
71 (Value::String(a), Value::String(b)) => a == b,
72 (Value::Boolean(a), Value::Boolean(b)) => a == b,
73 (Value::Null, Value::Null) => true,
74 (Value::Undefined, Value::Undefined) => true,
75 (Value::Number(a), Value::String(b)) => a.to_string() == b,
76 (Value::String(a), Value::Number(b)) => a == b.to_string(),
77 (Value::Number(a), Value::Boolean(b)) => a == if b { 1.0 } else { 0.0 },
78 (Value::Boolean(a), Value::Number(b)) => (if a { 1.0 } else { 0.0 }) == b,
79 _ => false,
80 };
81
82 stack.push(Value::Boolean(result));
83 Ok(())
84 }
85
86 /// Checks if two values are not equal using loose equality
87 ///
88 /// Pops two values from the stack, compares them using JavaScript-like
89 /// type coercion, and pushes the boolean result.
90 ///
91 /// # Arguments
92 /// * `stack` - The stack to operate on
93 ///
94 /// # Returns
95 /// * `Ok(())` on success
96 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
97 pub fn not_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
98 where
99 S: StackOperations,
100 {
101 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
102 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
103
104 let result = match (a, b) {
105 (Value::Number(a), Value::Number(b)) => a != b,
106 (Value::String(a), Value::String(b)) => a != b,
107 (Value::Boolean(a), Value::Boolean(b)) => a != b,
108 (Value::Null, Value::Null) => false,
109 (Value::Undefined, Value::Undefined) => false,
110 (Value::Number(a), Value::String(b)) => a.to_string() != b,
111 (Value::String(a), Value::Number(b)) => a != b.to_string(),
112 (Value::Number(a), Value::Boolean(b)) => a != if b { 1.0 } else { 0.0 },
113 (Value::Boolean(a), Value::Number(b)) => (if a { 1.0 } else { 0.0 }) != b,
114 _ => true,
115 };
116
117 stack.push(Value::Boolean(result));
118 Ok(())
119 }
120
121 /// Checks if two values are strictly equal
122 ///
123 /// Pops two values from the stack, compares them without type coercion,
124 /// and pushes the boolean result.
125 ///
126 /// # Arguments
127 /// * `stack` - The stack to operate on
128 ///
129 /// # Returns
130 /// * `Ok(())` on success
131 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
132 pub fn strict_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
133 where
134 S: StackOperations,
135 {
136 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
137 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
138
139 let result = a == b;
140 stack.push(Value::Boolean(result));
141 Ok(())
142 }
143
144 /// Checks if two values are strictly not equal
145 ///
146 /// Pops two values from the stack, compares them without type coercion,
147 /// and pushes the boolean result.
148 ///
149 /// # Arguments
150 /// * `stack` - The stack to operate on
151 ///
152 /// # Returns
153 /// * `Ok(())` on success
154 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
155 pub fn strict_not_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
156 where
157 S: StackOperations,
158 {
159 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
160 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
161
162 let result = a != b;
163 stack.push(Value::Boolean(result));
164 Ok(())
165 }
166
167 /// Checks if the first value is less than the second
168 ///
169 /// Pops two values from the stack, compares them, and pushes the boolean result.
170 /// Supports numeric comparison with type coercion.
171 ///
172 /// # Arguments
173 /// * `stack` - The stack to operate on
174 ///
175 /// # Returns
176 /// * `Ok(())` on success
177 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
178 pub fn less_than<S>(stack: &mut S) -> Result<(), ExecutionError>
179 where
180 S: StackOperations,
181 {
182 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
183 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
184
185 let result = match (a, b) {
186 (Value::Number(a), Value::Number(b)) => a < b,
187 (Value::String(a), Value::String(b)) => a < b,
188 (Value::Number(a), Value::String(b)) => a < b.parse::<f64>().unwrap_or(f64::NAN),
189 (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) < b,
190 _ => false,
191 };
192
193 stack.push(Value::Boolean(result));
194 Ok(())
195 }
196
197 /// Checks if the first value is less than or equal to the second
198 ///
199 /// Pops two values from the stack, compares them, and pushes the boolean result.
200 /// Supports numeric comparison with type coercion.
201 ///
202 /// # Arguments
203 /// * `stack` - The stack to operate on
204 ///
205 /// # Returns
206 /// * `Ok(())` on success
207 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
208 pub fn less_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
209 where
210 S: StackOperations,
211 {
212 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
213 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
214
215 let result = match (a, b) {
216 (Value::Number(a), Value::Number(b)) => a <= b,
217 (Value::String(a), Value::String(b)) => a <= b,
218 (Value::Number(a), Value::String(b)) => a <= b.parse::<f64>().unwrap_or(f64::NAN),
219 (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) <= b,
220 _ => false,
221 };
222
223 stack.push(Value::Boolean(result));
224 Ok(())
225 }
226
227 /// Checks if the first value is greater than the second
228 ///
229 /// Pops two values from the stack, compares them, and pushes the boolean result.
230 /// Supports numeric comparison with type coercion.
231 ///
232 /// # Arguments
233 /// * `stack` - The stack to operate on
234 ///
235 /// # Returns
236 /// * `Ok(())` on success
237 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
238 pub fn greater_than<S>(stack: &mut S) -> Result<(), ExecutionError>
239 where
240 S: StackOperations,
241 {
242 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
243 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
244
245 let result = match (a, b) {
246 (Value::Number(a), Value::Number(b)) => a > b,
247 (Value::String(a), Value::String(b)) => a > b,
248 (Value::Number(a), Value::String(b)) => a > b.parse::<f64>().unwrap_or(f64::NAN),
249 (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) > b,
250 _ => false,
251 };
252
253 stack.push(Value::Boolean(result));
254 Ok(())
255 }
256
257 /// Checks if the first value is greater than or equal to the second
258 ///
259 /// Pops two values from the stack, compares them, and pushes the boolean result.
260 /// Supports numeric comparison with type coercion.
261 ///
262 /// # Arguments
263 /// * `stack` - The stack to operate on
264 ///
265 /// # Returns
266 /// * `Ok(())` on success
267 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
268 pub fn greater_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
269 where
270 S: StackOperations,
271 {
272 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
273 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
274
275 let result = match (a, b) {
276 (Value::Number(a), Value::Number(b)) => a >= b,
277 (Value::String(a), Value::String(b)) => a >= b,
278 (Value::Number(a), Value::String(b)) => a >= b.parse::<f64>().unwrap_or(f64::NAN),
279 (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) >= b,
280 _ => false,
281 };
282
283 stack.push(Value::Boolean(result));
284 Ok(())
285 }
286
287 /// Performs logical AND operation
288 ///
289 /// Pops two values from the stack, performs logical AND, and pushes the result.
290 /// Uses JavaScript-like truthy/falsy conversion.
291 ///
292 /// # Arguments
293 /// * `stack` - The stack to operate on
294 ///
295 /// # Returns
296 /// * `Ok(())` on success
297 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
298 pub fn logical_and<S>(stack: &mut S) -> Result<(), ExecutionError>
299 where
300 S: StackOperations,
301 {
302 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
303 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
304
305 let a_truthy = is_truthy(&a);
306 let b_truthy = is_truthy(&b);
307
308 let result = a_truthy && b_truthy;
309 stack.push(Value::Boolean(result));
310 Ok(())
311 }
312
313 /// Performs logical OR operation
314 ///
315 /// Pops two values from the stack, performs logical OR, and pushes the result.
316 /// Uses JavaScript-like truthy/falsy conversion.
317 ///
318 /// # Arguments
319 /// * `stack` - The stack to operate on
320 ///
321 /// # Returns
322 /// * `Ok(())` on success
323 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
324 pub fn logical_or<S>(stack: &mut S) -> Result<(), ExecutionError>
325 where
326 S: StackOperations,
327 {
328 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
329 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
330
331 let a_truthy = is_truthy(&a);
332 let b_truthy = is_truthy(&b);
333
334 let result = a_truthy || b_truthy;
335 stack.push(Value::Boolean(result));
336 Ok(())
337 }
338
339 /// Performs logical NOT operation
340 ///
341 /// Pops one value from the stack, performs logical NOT, and pushes the result.
342 /// Uses JavaScript-like truthy/falsy conversion.
343 ///
344 /// # Arguments
345 /// * `stack` - The stack to operate on
346 ///
347 /// # Returns
348 /// * `Ok(())` on success
349 /// * `Err(ExecutionError::StackUnderflow)` if stack is empty
350 pub fn logical_not<S>(stack: &mut S) -> Result<(), ExecutionError>
351 where
352 S: StackOperations,
353 {
354 let value = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
355 let result = !is_truthy(&value);
356 stack.push(Value::Boolean(result));
357 Ok(())
358 }
359
360 /// Performs bitwise AND operation
361 ///
362 /// Pops two values from the stack, performs bitwise AND, and pushes the result.
363 /// Converts values to integers before operation.
364 ///
365 /// # Arguments
366 /// * `stack` - The stack to operate on
367 ///
368 /// # Returns
369 /// * `Ok(())` on success
370 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
371 pub fn bitwise_and<S>(stack: &mut S) -> Result<(), ExecutionError>
372 where
373 S: StackOperations,
374 {
375 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
376 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
377
378 let result = match (a, b) {
379 (Value::Number(a), Value::Number(b)) => {
380 Value::Number((a as i64 & b as i64) as f64)
381 }
382 _ => return Err(ExecutionError::TypeError("Cannot perform bitwise AND on non-numeric values".to_string())),
383 };
384
385 stack.push(result);
386 Ok(())
387 }
388
389 /// Performs bitwise OR operation
390 ///
391 /// Pops two values from the stack, performs bitwise OR, and pushes the result.
392 /// Converts values to integers before operation.
393 ///
394 /// # Arguments
395 /// * `stack` - The stack to operate on
396 ///
397 /// # Returns
398 /// * `Ok(())` on success
399 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
400 pub fn bitwise_or<S>(stack: &mut S) -> Result<(), ExecutionError>
401 where
402 S: StackOperations,
403 {
404 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
405 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
406
407 let result = match (a, b) {
408 (Value::Number(a), Value::Number(b)) => {
409 Value::Number((a as i64 | b as i64) as f64)
410 }
411 _ => return Err(ExecutionError::TypeError("Cannot perform bitwise OR on non-numeric values".to_string())),
412 };
413
414 stack.push(result);
415 Ok(())
416 }
417
418 /// Performs bitwise XOR operation
419 ///
420 /// Pops two values from the stack, performs bitwise XOR, and pushes the result.
421 /// Converts values to integers before operation.
422 ///
423 /// # Arguments
424 /// * `stack` - The stack to operate on
425 ///
426 /// # Returns
427 /// * `Ok(())` on success
428 /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
429 pub fn bitwise_xor<S>(stack: &mut S) -> Result<(), ExecutionError>
430 where
431 S: StackOperations,
432 {
433 let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
434 let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
435
436 let result = match (a, b) {
437 (Value::Number(a), Value::Number(b)) => {
438 Value::Number((a as i64 ^ b as i64) as f64)
439 }
440 _ => return Err(ExecutionError::TypeError("Cannot perform bitwise XOR on non-numeric values".to_string())),
441 };
442
443 stack.push(result);
444 Ok(())
445 }
446
447 /// Performs bitwise NOT operation
448 ///
449 /// Pops one value from the stack, performs bitwise NOT, and pushes the result.
450 /// Converts value to integer before operation.
451 ///
452 /// # Arguments
453 /// * `stack` - The stack to operate on
454 ///
455 /// # Returns
456 /// * `Ok(())` on success
457 /// * `Err(ExecutionError::StackUnderflow)` if stack is empty
458 pub fn bitwise_not<S>(stack: &mut S) -> Result<(), ExecutionError>
459 where
460 S: StackOperations,
461 {
462 let value = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
463
464 let result = match value {
465 Value::Number(n) => Value::Number(!(n as i64) as f64),
466 _ => return Err(ExecutionError::TypeError("Cannot perform bitwise NOT on non-numeric value".to_string())),
467 };
468
469 stack.push(result);
470 Ok(())
471 }
472}
473
474/// Determines if a value is truthy according to JavaScript rules
475///
476/// # Arguments
477/// * `value` - The value to check
478///
479/// # Returns
480/// * `true` if the value is truthy
481/// * `false` if the value is falsy
482fn is_truthy(value: &Value) -> bool {
483 match value {
484 Value::Boolean(b) => *b,
485 Value::Number(n) => *n != 0.0 && !n.is_nan(),
486 Value::String(s) => !s.is_empty(),
487 Value::Null => false,
488 Value::Undefined => false,
489 Value::Object(_) => true,
490 Value::Array(_) => true,
491 Value::Function(_) => true,
492 }
493}